#include "WebAPI.hpp"
#include <chrono>
#include <crow/app.h>
#include <crow/http_request.h>
#include <crow/http_response.h>
#include <crow/json.h>
#include <memory>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <thread>
#include <thrift/Thrift.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/protocol/TProtocol.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransport.h>

WebAPI::WebAPI()
    : transport(new apache::thrift::transport::TBufferedTransport(
          std::shared_ptr<apache::thrift::transport::TTransport>(
              new apache::thrift::transport::TSocket("localhost", 14001)))),
      client(std::shared_ptr<apache::thrift::protocol::TProtocol>(
          new apache::thrift::protocol::TBinaryProtocol(transport))) {
  CROW_ROUTE(app, "/api/ShotStatus")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json val = nlohmann::json::parse(req.body);
        if (val["UUID"].is_null()) {
          spdlog::warn("Receive unknow data from /api/ShotStatus");
          return crow::response(400);
        }
        bool status = client.ShotStatus(val["UUID"].get<std::string>());
        crow::json::wvalue ret({{"value", status}});
        return crow::response(ret);
      });

  CROW_ROUTE(app, "/api/GetDeviceList")
      .methods("POST"_method)([this](const crow::request &req) {
        std::set<std::string> callret;
        client.GetAllDevice(callret);
        nlohmann::json j = nlohmann::json::array();
        for (const auto &i : callret)
          j.push_back(i);
        return crow::response(j.dump());
      });

  CROW_ROUTE(app, "/api/StartShot")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json val = nlohmann::json::parse(req.body);
        if (val["UUID"].is_null()) {
          spdlog::warn("Receive unknow data from /api/StartShot");
          return crow::response(400);
        }
        client.StartShot(val["UUID"].get<std::string>());
        return crow::response(200);
      });

  CROW_ROUTE(app, "/api/StopShot")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json val = nlohmann::json::parse(req.body);
        if (val["UUID"].is_null()) {
          spdlog::warn("Receive unknow data from /api/StopShot");
          return crow::response(400);
        }
        client.StopShot(val["UUID"].get<std::string>());
        return crow::response(200);
      });

  CROW_ROUTE(app, "/api/GetSettingList")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json para = nlohmann::json::parse(req.body);
        if (para["UUID"].is_null()) {
          spdlog::warn("Receive unknow data from /api/GetSettingList");
          return crow::response(400);
        }
        std::string ret;
        client.GetSettingList(ret, para["UUID"]);
        return crow::response(ret);
      });
  CROW_ROUTE(app, "/api/GetSettingValue")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json para = nlohmann::json::parse(req.body);
        if (para["UUID"].is_null() || para["ID"].is_null()) {
          spdlog::warn("Receive unknow data from /api/GetSettingItem");
          return crow::response(400);
        }
        std::string ret;
        client.GetSettingValue(ret, para["UUID"].get<std::string>(),
                               para["ID"].get<long>());
        return crow::response(ret);
      });

  CROW_ROUTE(app, "/api/SetSettingValue")
      .methods("POST"_method)([this](const crow::request &req) {
        nlohmann::json para = nlohmann::json::parse(req.body);
        if (para["UUID"].is_null() || para["id"].is_null()) {
          spdlog::warn("Receive unknow data from /api/GetSettingItem");
          return crow::response(400);
        }
        if (client.SetSettingValue(para["UUID"], req.body))
          return crow::response(200);
        else
          return crow::response(400);
      });
  CROW_WEBSOCKET_ROUTE(app, "/api/preview")
      .onaccept([this](const crow::request &req, void **userdata) {
        return !wsPreview;
      })
      .onopen([this](crow::websocket::connection &conn) {
        spdlog::info("New Preview Request Enter");
        wsPreview = &conn;
      })
      .onclose([this](crow::websocket::connection &conn,
                      const std::string &reason) { wsPreview = nullptr; });
}

void WebAPI::Boot(int port) {
  bool connectFlag = false;
  do {
    try {
      transport->open();
      connectFlag = true;
    } catch (const apache::thrift::TException &e) {
      spdlog::warn("Can't Connect to peer server, retry after 5s, why={}",
                   e.what());
      std::this_thread::sleep_for(std::chrono::seconds(5));
    }
  } while (!connectFlag);

  workThread =
      std::thread([this, port]() { app.port(port).multithreaded().run(); });
}

WebAPI::~WebAPI() {
  if (workThread.joinable()) {
    app.stop();
    workThread.join();
    transport->close();
  }
}

void WebAPI::InputPreview(const std::vector<unsigned char> &rawData) {
  if (!wsPreview)
    return;
  wsPreview->send_binary(std::string(rawData.begin(), rawData.end()));
}
